//
//  LosslessOpentionValue.swift
//
//  Created by Ink on 2024/7/19.
//  Copyright © 2024 ZM. All rights reserved.
//

import BetterCodable

/// Decodes Codable values into their respective preferred types.
///
/// `@LosslessValueCodable` attempts to decode Codable types into their preferred order while preserving the data in the most lossless format.
///
/// The preferred type order is provided by a generic `LosslessDecodingStrategy` that provides an ordered list of `losslessDecodableTypes`.
@propertyWrapper
public struct LosslessOpentionValueCodable<Strategy: LosslessDecodingStrategy>: Codable {
    public static var defaultValue: Strategy.Value? { nil }
    
    private let type: LosslessStringCodable.Type?

    public var wrappedValue: Strategy.Value?

    public init(wrappedValue: Strategy.Value?) {
        self.wrappedValue = wrappedValue
        self.type = Strategy.Value.self
    }

    public init(from decoder: Decoder) {
        do {
            self.wrappedValue = try Strategy.Value.init(from: decoder)
            self.type = Strategy.Value.self
        } catch {
            if let rawValue = Strategy.losslessDecodableTypes.lazy.compactMap({ $0(decoder) }).first {
                self.wrappedValue = Strategy.Value.init("\(rawValue)")
                self.type = Swift.type(of: rawValue)
            }
            else {
                self.wrappedValue = nil
                self.type = nil
            }
        }
    }

    public func encode(to encoder: Encoder) {
        
        guard let wrappedValue = wrappedValue, let type = type else {
            return
        }
        let string = String(describing: wrappedValue)

        if let original = type.init(string) {
            try? original.encode(to: encoder)
        }
        else {
            let description = "Unable to encode '\(wrappedValue)' back to source type '\(type)'"
            print(description)
            
        }
    }
}

extension KeyedDecodingContainer {

    /// Default implementation of decoding a DefaultCodable
    ///
    /// Decodes successfully if key is available if not fallsback to the default value provided.
    public func decode<P>(_: LosslessOpentionBoolValue<P>.Type, forKey key: Key) throws -> LosslessOpentionBoolValue<P> {
        if let value = try decodeIfPresent(LosslessOpentionBoolValue<P>.self, forKey: key) {
            return value
        } else {
            return LosslessOpentionBoolValue(wrappedValue: LosslessOpentionBoolValue<P>.defaultValue)
        }
    }
    
    /// Default implementation of decoding a DefaultCodable
    ///
    /// Decodes successfully if key is available if not fallsback to the default value provided.
    public func decode<P>(_: LosslessOpentionValue<P>.Type, forKey key: Key) throws -> LosslessOpentionValue<P> {
        if let value = try decodeIfPresent(LosslessOpentionValue<P>.self, forKey: key) {
            return value
        } else {
            return LosslessOpentionValue(wrappedValue: LosslessOpentionValue<P>.defaultValue)
        }
    }
}


extension LosslessOpentionValueCodable: Equatable where Strategy.Value: Equatable {
    public static func == (lhs: LosslessOpentionValueCodable<Strategy>, rhs: LosslessOpentionValueCodable<Strategy>) -> Bool {
        return lhs.wrappedValue == rhs.wrappedValue
    }
}

extension LosslessOpentionValueCodable: Hashable where Strategy.Value: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(wrappedValue)
    }
}

public typealias LosslessOpentionValue<T> = LosslessOpentionValueCodable<LosslessDefaultStrategy<T>> where T: LosslessStringCodable

public typealias LosslessOpentionBoolValue<T> = LosslessOpentionValueCodable<LosslessBooleanStrategy<T>> where T: LosslessStringCodable


// MARK: - KeyedDecodingContainer
public protocol StringCodableStrategy: DefaultCodableStrategy where DefaultValue == String {}

public struct DefaultEmptyStringStrategy: StringCodableStrategy {
    public static var defaultValue: String { return "" }
}

/// Decodes Bools defaulting to `true` if applicable
///
/// `@DefaultTrue` decodes Bools and defaults the value to true if the Decoder is unable to decode the value.
public typealias DefaultEmptyString = DefaultCodable<DefaultEmptyStringStrategy>
